/*
 	keycounter.cc

 	Copyright (c) 2008 Bojan Rajkovic

 	Permission is hereby granted, free of charge, to any person obtaining a copy
 	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
 	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 	copies of the Software, and to permit persons to whom the Software is
 	furnished to do so, subject to the following conditions:
	
 	The above copyright notice and this permission notice shall be included in
 	all copies or substantial portions of the Software.

 	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 	THE SOFTWARE.
*/

#include "../include/keycounter.h"

extern int optind;
ifstream fd;
ofstream fo;
unsigned long keys = 0;
time_t last_pulse;

void terminate(int parameter) {
	utility::log_message("Caught a sigterm, will shut down now.", M_INFO);
	fd.close();
	fo.close();
	pulse::save_pulse_file(keys);
	exit(EXIT_SUCCESS);
}

void hangup(int parameter) {
	char* logMessage;
	struct pulse_info pulse;
	pulse.keys = keys;
	Config configuration;
	configuration.readFile(strdup(DEFAULT_CONFIG_FILE));
	pulse.user_name = (const char*)configuration.lookup("application.pulse.username");
	pulse.password = (const char*)configuration.lookup("application.pulse.password");
	if ((bool)configuration.lookup("application.pulse.toweb")) {
		int retval = pulse::pulse_to_web(pulse);
		if (retval == HTTP_STATUS_CODE_OK) {			
			asprintf(&logMessage, "Successfully pulsed to %s at %s, %li keys sent.",
			(const char*)configuration.lookup("application.pulse.server"), 
			utility::get_current_date_time(), keys);
			utility::log_message(logMessage, M_INFO);
			free(logMessage);
			keys = 0; // reset the key count, we just sent it out to the server
			pulse::zero_pulse_file(); // reset the on-disk file, since we just sent off its info
			last_pulse = time(NULL);
		}
	}
	if ((bool)configuration.lookup("application.pulse.todb")) {
		int retval = pulse::pulse_to_db(pulse);
		if (retval == SQLITE_OK) {
			asprintf(&logMessage, "Successfully pulsed to database at %s, %li keys sent.",
			utility::get_current_date_time(), keys);
			utility::log_message(logMessage, M_INFO);
			free(logMessage);
			keys = 0;
			pulse::zero_pulse_file();
			last_pulse = time(NULL);
		}
	}
}

void interrupt(int parameter) {
	utility::log_message("Caught a sigint, will shut down now.", M_INFO);
	fd.close();
	fo.close();
	pulse::save_pulse_file(keys);
	exit(EXIT_SUCCESS);
}

int main(int argc, char* argv[]) {
	
	setlocale (LC_ALL, "");
  	textdomain ("keycounter");
  	bindtextdomain ("keycounter", LOCALEDIR);
  	
	// Set up the variables we'll be using.
	char* logMessage;
	unsigned long session_keys = 0;
	time_t startup_time;
	int opt, longopt_index = 0;
	bool output_verbose = false, daemonize = true;
	// Grab defaults for these 3, even though we'll be reading them out of the configuration
	// and then overwriting that with command-line options, if there are any.
	char *event_device = strdup(DEFAULT_INPUT_DEVICE);
	char *output_file = strdup(DEFAULT_OUTPUT_FILE);
	char *config_file = strdup(DEFAULT_CONFIG_FILE);
	
	// Let's also set catches for SIGTERM and SIGINT--we just wanna log that we're terminating
	// there's nothing to actually clean up when terminated
	void (*prev_fn)(int);
	prev_fn = signal (SIGTERM,terminate);
  	if (prev_fn==SIG_IGN) signal(SIGTERM,SIG_IGN);
	prev_fn = signal (SIGINT,interrupt);
  	if (prev_fn==SIG_IGN) signal(SIGINT,SIG_IGN);
  	prev_fn = signal (SIGHUP,hangup);
  	if (prev_fn==SIG_IGN) signal(SIGHUP,SIG_IGN);
	
	// Let's parse the config file, and pull relevant settings out of it
	// We'll let them get overwritten by command-line settings as appropriate
	Config configuration;
	configuration.readFile(config_file);
	
	event_device = (char*)(const char*)configuration.lookup("application.system.eventdevice");
	output_file = (char*)(const char*)configuration.lookup("application.output.outputfile");
	output_verbose = (bool)configuration.lookup("application.output.verbose");
	daemonize = (bool)configuration.lookup("application.system.daemonize");
	
	
	// Before we do anything at all, let's see if the pid file exists. If it does, this
	// means that either another instance is still running, or that another instance wasn't
	// closed by means of the init script.
	if (access("/var/run/keycounter.pid", F_OK) == 0) {
		utility::log_message(_("PID file exists, let's check if anything's in it and if it's valid."), M_INFO);
		if (utility::check_pid_file("/var/run/keycounter.pid")) {
			utility::log_message("PID file exists, inside pid is valid, dying.", M_ERROR);
			cerr << "Oops, it looks like there's already an instance running: /var/run/keycounter.pid" << endl;
			cerr << "exists and the PID inside appears to be an existing process." << endl;
			exit(EXIT_FAILURE);
		} else {
			utility::log_message("PID file exists, inside PID is not valid -- keep on truckin'.", M_INFO);
		}
	}

	static struct option long_options[] = {
		{"event-device", 1, NULL, 'e'},
		{"output-file", 1, NULL, 'o'},
		{"verbose-output", 0, NULL, 'v'},
		{"no-daemon", 0, NULL, 'n'},
		{"help", 0, NULL, 'h'},
		{"usage", 0, NULL, 'h'},
		{NULL, 0, NULL, 0} /* marks end-of-list */
	};

	while ((opt = getopt_long(argc, argv, "e:o:vnh", long_options, &longopt_index)) > 0) {
		switch (opt) {
		case 'e':
			event_device = optarg;
			break;
		case 'o':
			output_file = optarg;
			break;
		case 'v':
			output_verbose = true;
			break;
		case 'n':
			daemonize = false;
			break;
		case '?':
		case 'h':
		default:
			fprintf(stderr, "usage: %s [options]\n", basename(argv[0]));
			fprintf(stderr, "  -e, --event-device ARG   The event device to poll when trying to read keystrokes (default: /dev/input/event0). \n");
			fprintf(stderr, "  -n, --no-daemon	Don't daemonize the keycounter (default: false). \n");
			fprintf(stderr, "  -o, --output-file ARG     The file to output keystroke statistics to (default: /var/keycounter). \n");
			fprintf(stderr, "  -v, --verbose	Output verbose, easily human-readable statistics to the output file (default: false). \n");
 			exit(EXIT_SUCCESS);
		}
	}

	// Okay, so we've parsed the input, we probably should validate it
	// and either die or fall back to a sane default

	// If the event device path is blank or it doesn't exist, fall back to /dev/input/event0
	if (access(event_device, F_OK) != 0) {
		asprintf(&logMessage, "Event device file %s does not exist, cannot continue.", event_device);		
		utility::log_message(logMessage, M_ERROR);
		free(logMessage);
		cerr << "Event device file " << event_device << " does not exist, cannot continue." << endl;
	}
	
	// Before we start, we'll output both to the log:
	asprintf(&logMessage, "Event device path: %s", event_device);
	utility::log_message(logMessage, M_INFO);
	free(logMessage);
	asprintf(&logMessage, "Output file path: %s", output_file);
	utility::log_message(logMessage, M_INFO);
	free(logMessage);

	// Do we actually want to daemonize?
	if (daemonize) {

		utility::log_message("Daemonizing keycounter...", M_INFO);
		
		// Fork the parent process
		pid_t pid;
		pid = fork();

		// Write the child's PID to the log and to the pid file so we can kill the daemon.
		ofstream pid_file ("/var/run/keycounter.pid", ios::trunc);
		if (!pid_file.good()) {
			utility::log_message("Could not open PID file for writing, maybe the daemon isn't running as root?", M_ERROR);
			exit(EXIT_FAILURE);
		}

		asprintf(&logMessage, "Child PID: %i", pid);
		utility::log_message(logMessage, M_INFO);
		free(logMessage);
		pid_file << pid << endl;
		pid_file.close();

		// If we got a bad PID, fail out, if we got a good PID, we're all set.
		if (pid < 0) {
			exit(EXIT_FAILURE);
		}
		if (pid > 0) {
			exit(EXIT_SUCCESS);
		}

		// Past this point, we're working entirely with the child process
		// The parent process has been killed by the above exit().

		// Set the umask so we can access files written by the daemon
		umask(0);

		// Get a new SID
		pid_t sid;
		sid = setsid();

		if (sid < 0) {
			utility::log_message("Could not get valid SID for keycounter daemon, exiting.", M_ERROR);
			exit(EXIT_FAILURE);
		}

		if ((chdir("/")) < 0) {
			utility::log_message("Could not change to the root directory, exiting.", M_ERROR);
			exit(EXIT_FAILURE);
		}

		// Close all the standard file descriptors
		close(STDIN_FILENO);
		close(STDOUT_FILENO);
		close(STDERR_FILENO);

		utility::log_message("Finished daemonizing...", M_INFO);

	} else {
		asprintf(&logMessage, "Not daemonizing, PID %i.", getpid());
		utility::log_message(logMessage, M_INFO);
		if (logMessage != NULL) { free(logMessage); }
	}

	// Get our startup time
	time(&startup_time);

	// Open the stream for reading from the event device--arguably, the most important part. ;)
	// Also open the output file.
	fd.open(event_device, ios::binary);
	fo.open(output_file, ios::trunc);
	
	// Let's clean up some memory
	free(event_device);
	free(output_file);
	free(config_file);

	// If the streams are no good, bail.
	if(!fd.good()) {
		asprintf(&logMessage, "Failed to open %s, make sure the daemon is running as root.", event_device);
		utility::log_message(logMessage, M_ERROR);
		free(logMessage);
		exit(EXIT_FAILURE);
	}
	if (!fo.good()) {
		asprintf(&logMessage, "Failed to open %s, make sure the daemon is running as root.", output_file);
		utility::log_message(logMessage, M_ERROR);
		free(logMessage);
		exit(EXIT_FAILURE);
	}
	
	// Let's drop our root privileges
	// We're gonna use the nobody user.
	struct passwd* pw = getpwnam("nobody");
	setuid(pw->pw_uid);
	setgid(pw->pw_gid);
	utility::log_message("Dropped our root privileges, proceeding as 'nobody' user.", M_INFO);

	// And the big loop.

	while (1) {
		// Read the struct from the event device reader
		struct input_event ev;
		fd.read( reinterpret_cast<char *> (&ev), sizeof(struct input_event) );
		// If there was a keystroke
		if (ev.value == 1) {
			keys++; // increment the count of keys since last pulse
			session_keys++; // increment the count of keys for this session
		
			// Write everything out to the output file
			// First, we gotta rewind the output stream
			fo.seekp(0, ios_base::beg);

			// Get lots of system info, pull out the uptime
			struct sysinfo s_info;
			sysinfo(&s_info);
			float uptime = s_info.uptime/60;

			// Instead of using clock() to get our own uptime, we'll just subtract 2 time_t's -- now, and when we started
			// clock() gives us CPU time, not real time, which is totally useless
			float program_uptime = (time(NULL) - startup_time)/60; // this gives us the number of minutes we've been running
			float since_last_pulse = (time(NULL) - last_pulse)/60; // this gives us the number of minutes since the last pulse

			// Format the startup time of the program for display on the first line of verbose output
			// Also do last pulse
			char* startup_time_string = ctime(&startup_time);
			char* last_pulse_string = ctime(&last_pulse);
			// Trim that annoying newline out of ctime
			if (startup_time_string[strlen(startup_time_string)-1] == '\n') {
				startup_time_string[strlen(startup_time_string)-1] = 0;
			}
			if (last_pulse_string[strlen(last_pulse_string)-1] == '\n') {
				last_pulse_string[strlen(last_pulse_string)-1] = 0;
			}

			if (output_verbose) {
				fo << session_keys << " total keys pressed since keycounter was started up on " << startup_time_string << ", " << program_uptime << " minutes ago." << endl;
				fo << keys << " total keys pressed since the last pulse on " << last_pulse << ", " << since_last_pulse << " minutes ago." << endl;
				fo << "This is an average of " << session_keys/program_uptime << " keys per minute since the keycounter started." << endl;
				fo << "This is an average of " << session_keys/uptime << " keys per minute since the system started, " << uptime << " minutes ago." << endl;
				free(startup_time_string);
			} else {
				char *outputFile;
				asprintf(&outputFile, "%li|%li|%f|%f|%f|%i", keys, session_keys, program_uptime, keys/program_uptime, keys/uptime, (int)last_pulse);
				fo << outputFile << endl;
				free(outputFile);
				outputFile = NULL;
			}
		}
	}
}
